package alan.springboot2x.zkConfigStart.zookeeper;

import alan.springboot2x.zkConfigStart.scope.RefreshScopeRegistry;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author: Alan
 * @date: 2021/3/21 22:53
 */
@AutoConfigureAfter(RefreshScopeRegistry.class)
@Slf4j
public class CuratorUtil {

    @Value("${zookeeper.client:127.0.0.1:2181}")
    private String connectStr;

    @Value("${zookeeper.config.enable:false}")
    private boolean enbale;;

    private static String path = "/config";

    private static String zkPropertyName = "zookeeperSource";

    private static String scopeName = "AlanRefresh";

    private static CuratorFramework client;

    private ConcurrentHashMap map = new ConcurrentHashMap<>();

    @Autowired
    private Environment environment;

    @Autowired
    private ConfigurableApplicationContext applicationContext;


    private BeanDefinitionRegistry beanDefinitionRegistry;

    @PostConstruct
    public void init(){
        if(!enbale) return;;

        RefreshScopeRegistry refreshScopeRegistry =  applicationContext.getBean(RefreshScopeRegistry.class);
        beanDefinitionRegistry = refreshScopeRegistry.getBeanDefinitionRegistry();

        client = CuratorFrameworkFactory.builder()
                .connectString(connectStr)
                .sessionTimeoutMs(5000).retryPolicy(new ExponentialBackoffRetry(1000,3))
                .build();

        client.start();

        try {
            Stat stat = client.checkExists().forPath(path);
            if(stat == null){
                //创建一个/config节点
                client.create().creatingParentContainersIfNeeded().withMode(CreateMode.EPHEMERAL)
                        .forPath(path,"zookeeper config".getBytes());
                TimeUnit.SECONDS.sleep(1);
            }else {
                addChildToSpringProperties(client,path);
            }
            childNodeCache(client,path);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("error:{}", e);
        }

    }

    private void childNodeCache(CuratorFramework client,String path){
        try {
            final PathChildrenCache pathChildrenCache = new PathChildrenCache(client, path, true);
            pathChildrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);

            pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
                @Override
                public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                    switch (event.getType()){
                        case CHILD_ADDED:
                            log.info("add node");
                            addEnv(event.getData(),client);
                            break;
                        case CHILD_REMOVED:
                            log.info("del node");
                            delEnv(event.getData(),client);
                            break;
                        case CHILD_UPDATED:
                            log.info("update node");
                            addEnv(event.getData(),client);
                            break;
                        default:
                            break;
                    }
                    refreshBean();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void refreshBean() {
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanDefinitionName);
            if(beanDefinition.getScope().equalsIgnoreCase(scopeName)){
                //删除map中的对象
                applicationContext.getBeanFactory().destroyScopedBean(beanDefinitionName);
                //在次实例化
                applicationContext.getBean(beanDefinitionName);
            }
        }

    }

    private void delEnv(ChildData data, CuratorFramework client) {
        ChildData next = data;
        // /config/test.user.name
        String childPath = next.getPath();

        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        PropertySource<?> propertySource = propertySources.get(zkPropertyName);
        ConcurrentHashMap source = (ConcurrentHashMap) propertySource.getSource();

        source.remove(childPath.substring(path.length() + 1));

    }

    private void addEnv(ChildData data, CuratorFramework client) {
        ChildData next = data;
        // /config/test.user.name
        String childPath = next.getPath();
        String string;
        try {
            string = new String(client.getData().forPath(childPath));

            MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
            PropertySource<?> propertySource = propertySources.get(zkPropertyName);
            ConcurrentHashMap source = (ConcurrentHashMap) propertySource.getSource();

            source.put(childPath.substring(path.length() + 1),string);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void addChildToSpringProperties(CuratorFramework client, String path) {
        //把zookeeper里的配置属性值也封装与PropertySource对象，然后把这个对象设置到environment中
        if (!checkExistsSpringProperty()) {
            createZookeeperSpringProperty();
        }
        //加载zookeeper 里面/config节点下面的节点
        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        PropertySource<?> propertySource = propertySources.get(zkPropertyName);
        ConcurrentHashMap zkmap = (ConcurrentHashMap) propertySource.getSource();
        List<String> stringList = null;
        try {
            stringList = client.getChildren().forPath(path);
            for (String string : stringList) {
                //填充source对象
                zkmap.put(string,new String(client.getData().forPath(path+"/"+string)) );
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createZookeeperSpringProperty() {
        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        OriginTrackedMapPropertySource zkPropertySource = new OriginTrackedMapPropertySource(zkPropertyName,map);
        propertySources.addFirst(zkPropertySource);
    }

    private boolean checkExistsSpringProperty() {
        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        for (PropertySource<?> propertySource : propertySources) {
            if( zkPropertyName.equalsIgnoreCase(propertySource.getName())){
                return true;
            }
        }
        return false;
    }


}
